package jmine.tec.hydra.receive.impl.file;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

import jmine.tec.hydra.HydraController;
import jmine.tec.hydra.HydraMessages;
import jmine.tec.hydra.domain.HydraSystem;
import jmine.tec.hydra.exception.HydraException;
import jmine.tec.hydra.util.monitor.DirectoryListener;
import jmine.tec.hydra.util.monitor.SystemDirectory;

import org.apache.log4j.Logger;

import bancosys.tec.persist.dao.BaseDAO;
import bancosys.tec.persist.dao.BeanNotFoundException;

/**
 * Implementação de Receiver para arquivos.
 * 
 * @author piercio
 */
public class DirectoryChangedListener implements DirectoryListener {

    private static final Logger LOG = Logger.getLogger(DirectoryChangedListener.class);

    public static final String FILE_NAME = "fileName";

    private HydraController controller;

    private boolean renameFiles = true;

    private String fileExtension = "xml";

    private HydraFileFilter filter;

    /**
     * Indica que recebeu houve alteração no diretório de entrada de mensagens
     * 
     * @param sysdir a tupla diretório/sistema.
     */
    public void directoryChanged(SystemDirectory sysdir) {
        File directory = sysdir.getDirectory();
        if (directory.isDirectory()) {
            File[] files = directory.listFiles(this.getFilter());
            LOG.info("... " + files.length + " files matched");
            for (File file : files) {
                this.handleFile(sysdir.getSystem(), file);
            }
        }
    }

    /**
     * Manipula um arquivo.
     * 
     * @param systemName nome do sistema.
     * @param file arquivo sendo tratado.
     */
    private void handleFile(String systemName, File file) {
        LOG.info("... Executing " + file.getName());
        File renamedFile = new File(file.getAbsolutePath() + PROCESSING);
        if (file.renameTo(renamedFile)) {
            boolean success = false;
            FileInputStream input = null;
            try {
                this.controller.getPersistenceController().startTransaction();
                input = new FileInputStream(renamedFile);
                this.callHydra(systemName, renamedFile, input);
                success = true;
            } catch (HydraException e) {
                throw new HydraException(HydraMessages.RECEIVE_FILE_ERROR.create(renamedFile.getName()), e);
            } catch (IOException e) {
                throw new HydraException(HydraMessages.RECEIVE_FILE_ERROR.create(renamedFile.getName()), e);
            } finally {
                this.closeInputStreamAndRenameFile(renamedFile, file, input, success);
                if (success) {
                    this.controller.getPersistenceController().commit();
                } else {
                    this.controller.getPersistenceController().rollback();
                }
            }
        } else {
            LOG.info("Não foi possível renomear o arquivo " + file.getName());
        }
    }

    /**
     * Delega ao Hydra o tratamento do arquivo.
     * 
     * @param systemName nome do sistema.
     * @param file arquivo sendo tratado.
     * @param input {@link InputStream} do arquivo sendo tratado.
     */
    private void callHydra(String systemName, File file, InputStream input) {
        HydraSystem system = this.findSystem(systemName);
        Map<String, String> properties = new HashMap<String, String>();
        properties.put(FILE_NAME, file.getName());

        this.controller.receive(system, input, properties);
    }

    /**
     * Fecha o {@link InputStream} e renomeia o arquivo.
     * 
     * @param modifiedFile arquivo sendo tratado.
     * @param originalFile arquivo que terá o status alterado.
     * @param input {@link InputStream} do arquivo sendo tratado.
     * @param success se o arquivo foi processado com sucesso.
     */
    private void closeInputStreamAndRenameFile(File modifiedFile, File originalFile, FileInputStream input, boolean success) {
        try {
            if (input != null) {
                input.close();
            }
            if (this.renameFiles) {
                modifiedFile.renameTo(new File(originalFile.getAbsolutePath() + (success ? SUCESSO : FALHA)));
            }
        } catch (IOException e) {
            LOG.error("Erro fechando stream do arquivo " + originalFile.getName(), e);
        }
    }

    /**
     * @param systemName nome do sistema.
     * @return sistema.
     */
    private HydraSystem findSystem(String systemName) {
        BaseDAO<HydraSystem> sysDAO = this.controller.getDAOFactory().getGenericDAO(HydraSystem.class);
        try {
            return sysDAO.findByNaturalKey(systemName);
        } catch (BeanNotFoundException e) {
            throw new HydraException(HydraMessages.RECEIVE_FILE_RECEIVER.create(systemName), e);
        }
    }

    /**
     * Define o controller.
     * 
     * @param controller o controller.
     */
    public void setController(HydraController controller) {
        this.controller = controller;
    }

    /**
     * Define se deve ou não renomear os arquivos lidos.
     * 
     * @param renameFiles <code>true</code> se deve renomear, <code>false</code> caso contrário.
     */
    public void setRenameFiles(boolean renameFiles) {
        this.renameFiles = renameFiles;
    }

    /**
     * @param fileExtension the fileExtension to set
     */
    public void setFileExtension(String fileExtension) {
        this.fileExtension = fileExtension;
        if (fileExtension != null) {
            this.setFilter(new HydraFileFilter(this.fileExtension));
        }
    }

    /**
     * @return the file extension
     */
    public String getFileExtension() {
        return this.fileExtension;
    }

    /**
     * @return the filter
     */
    public HydraFileFilter getFilter() {
        if (this.filter == null && this.fileExtension != null) {
            this.filter = new HydraFileFilter(this.fileExtension);
        }
        return this.filter;
    }

    /**
     * @param filter the filter to set
     */
    public void setFilter(HydraFileFilter filter) {
        this.filter = filter;
    }
}